package com.crane.common.framework.shiro;

import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.SerializationUtils;

import java.io.Serializable;
import java.util.*;

public class ShiroRedisCache<K, V> implements Cache<K, V> {

    private int expire;

    private String keyPrefix;

    private RedisTemplate<Object, Serializable> redisTemplate;

    public ShiroRedisCache(int expire, String keyPrefix, RedisTemplate<Object, Serializable> redisTemplate) {
        this.expire = expire;
        this.keyPrefix = keyPrefix;
        this.redisTemplate = redisTemplate;
    }

    private byte[] getByteKey(K key) {
        if (key instanceof String) {
            String preKey = this.keyPrefix + key;
            return preKey.getBytes();
        } else {
            return SerializationUtils.serialize((Serializable) key);
        }
    }

    @Override
    public void clear() throws CacheException {
        redisTemplate.execute(new RedisCallback<Void>() {
            @Override
            public Void doInRedis(RedisConnection connection) throws DataAccessException {
                connection.flushDb();
                return null;
            }
        });
    }

    @Override
    public V get(K key) throws CacheException {
        if (key == null) {
            return null;
        } else {
            final byte[] byteKey = getByteKey(key);
            redisTemplate.execute(new RedisCallback<V>() {
                @Override
                public V doInRedis(RedisConnection connection) throws DataAccessException {
                    if (connection.exists(byteKey)) {
                        byte[] value = connection.get(byteKey);
                        return (V) SerializationUtils.deserialize(value);
                    }
                    return null;
                }
            });
        }
        return null;
    }

    @Override
    public Set<K> keys() {
        final byte[] byteKey = (this.keyPrefix + "*").getBytes();
        redisTemplate.execute(new RedisCallback<Set<K>>() {
            @Override
            public Set<K> doInRedis(RedisConnection connection) throws DataAccessException {
                Set<byte[]> keys = connection.keys(byteKey);
                Set<K> result = new HashSet<>();
                for (byte[] temp : keys) {
                    K k = (K) SerializationUtils.deserialize(temp);
                    result.add(k);
                }
                return result;
            }
        });
        return null;
    }

    @Override
    public V put(K key, V value) throws CacheException {
        final byte[] byteKey = getByteKey(key);
        final byte[] byteValue = SerializationUtils.serialize((Serializable) value);
        redisTemplate.execute(new RedisCallback<Void>() {
            @Override
            public Void doInRedis(RedisConnection connection) throws DataAccessException {
                connection.setEx(byteKey, expire, byteValue);
                return null;
            }
        });
        return value;
    }

    @Override
    public V remove(K key) throws CacheException {
        final byte[] byteKey = getByteKey(key);
        redisTemplate.execute(new RedisCallback<Void>() {
            @Override
            public Void doInRedis(RedisConnection connection) throws DataAccessException {
                connection.del(new byte[][]{byteKey});
                return null;
            }
        });
        return null;
    }

    @Override
    public int size() {
        redisTemplate.execute(new RedisCallback<Integer>() {
            @Override
            public Integer doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.dbSize().intValue();
            }
        });
        return 0;
    }

    @Override
    public Collection<V> values() {

        final byte[] byteKey = (this.keyPrefix + "*").getBytes();
        redisTemplate.execute(new RedisCallback<List<V>>() {
            @Override
            public List<V> doInRedis(RedisConnection connection) throws DataAccessException {
                Set<byte[]> keys = connection.keys(byteKey);
                List<byte[]> valueByte = connection.mGet(keys.toArray(new byte[keys.size()][]));
                List<V> result = new ArrayList<>();

                for (byte[] temp : valueByte) {
                    V v = (V) SerializationUtils.deserialize(temp);
                    result.add(v);
                }
                return result;
            }
        });
        return null;
    }


}
